package com.pencilisland.pay.api.service.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.imageio.ImageIO;
import javax.xml.bind.annotation.XmlElement;

import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.pencilisland.pay.api.bean.constant.WxpayConstant;
import com.pencilisland.pay.api.bean.wxpay.WxLoginResp;
import com.pencilisland.pay.api.bean.wxpay.WxpayAppSubmitReq;
import com.pencilisland.pay.api.bean.wxpay.WxpayCloseOrderReq;
import com.pencilisland.pay.api.bean.wxpay.WxpayJsapiSubmitReq;
import com.pencilisland.pay.api.bean.wxpay.WxpayOrderReq;
import com.pencilisland.pay.api.bean.wxpay.WxpayOrderResp;
import com.pencilisland.pay.api.bean.wxpay.WxpaySandboxSignKeyReq;
import com.pencilisland.pay.api.bean.wxpay.WxpaySandboxSignKeyResp;
import com.pencilisland.pay.api.bean.wxpay.WxpaySubmitReq;
import com.pencilisland.pay.api.config.AppConfig;
import com.pencilisland.pay.api.service.AliOssService;
import com.pencilisland.pay.api.service.WxpayService;
import com.pencilisland.pay.api.util.G;
import com.pencilisland.pay.api.util.JaxbUtil;
import com.pencilisland.pay.api.util.MatrixToImageWriter;
import com.pencilisland.pay.api.util.RandomUtil;

@Component
public class WxpayServiceImpl implements WxpayService {
	Logger log = LoggerFactory.getLogger(getClass());

	@Autowired
	AppConfig appConfig;
	@Autowired
	private RestTemplate rest;
	@Autowired
	AliOssService aliOssService;
	@Autowired
	ObjectMapper objectMapper;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.pencilisland.pay.api.service.impl.WxpayService#order(com.pencilisland
	 * .pay.api.bean.wxpay.WxpayOrderReq)
	 */
	@Override
	public WxpaySubmitReq order(WxpayOrderReq request) throws Exception {

		// String signKey = getSandboxSignKey();
		String signKey = appConfig.getWxpaySignKey();

		request.setAppId(appConfig.getWxappAppId());
		request.setMchId(appConfig.getWxpayMchId());
		request.setDeviceInfo("WEB");
		request.setNonceStr(RandomUtil.generateString(20));

		// 生成签名串
		request.setSign(wxpaySign(request, signKey));

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Arrays.asList(WxpayConstant.APPLICAIONT_XML_UTF8));
		headers.setContentType(WxpayConstant.APPLICAIONT_XML_UTF8);

		HttpEntity<String> httpEntity = new HttpEntity<>(JaxbUtil.convertToXml(request), headers);
		String respXml = rest.postForObject(WxpayConstant.ORDER_URL, httpEntity, String.class);
		log.info("统一下单返回【{}】", respXml);
		if (!StringUtils.isEmpty(respXml)) {
			WxpayOrderResp resp = JaxbUtil.converyToJavaBean(respXml, WxpayOrderResp.class);
			if (resp != null && WxpayConstant.SUCCESS.equals(resp.getReturnCode())
					&& WxpayConstant.SUCCESS.equals(resp.getResultCode())) {

				WxpaySubmitReq submitReq = new WxpaySubmitReq();

				if (WxpayConstant.APP.equals(request.getTradeType())) {
					WxpayAppSubmitReq req = new WxpayAppSubmitReq();
					req.setAppId(resp.getAppId());
					req.setPartnerId(resp.getMchId());
					req.setPrepayId(resp.getPrepayId());
					req.setPackageStr("Sign=WXPay");
					req.setTimeStamp(G.str(System.currentTimeMillis() / 1000));
					req.setNonceStr(RandomUtil.generateString(20));
					req.setSign(wxpaySign(req, signKey));

					submitReq.setAppId(req.getAppId());
					submitReq.setPartnerId(req.getPartnerId());
					submitReq.setPrepayId(req.getPrepayId());
					submitReq.setPackageStr(req.getPackageStr());
					submitReq.setTimeStamp(req.getTimeStamp());
					submitReq.setNonceStr(req.getNonceStr());
					submitReq.setSign(req.getSign());
				} else if (WxpayConstant.JSAPI.equals(request.getTradeType())) {
					WxpayJsapiSubmitReq req = new WxpayJsapiSubmitReq();
					req.setAppId(resp.getAppId());
					req.setNonceStr(RandomUtil.generateString(20));
					req.setPackageStr("prepay_id=" + resp.getPrepayId());
					req.setSignType("MD5");
					req.setTimeStamp(G.str(System.currentTimeMillis() / 1000));
					req.setPaySign(wxpaySign(req, signKey));

					submitReq.setAppId(req.getAppId());
					submitReq.setNonceStr(req.getNonceStr());
					submitReq.setPackageStr(req.getPackageStr());
					submitReq.setSignType(req.getSignType());
					submitReq.setTimeStamp(req.getTimeStamp());
					submitReq.setSign(req.getPaySign());
					submitReq.setPrepayId(resp.getPrepayId());
					submitReq.setPartnerId(resp.getMchId());
				} else if (WxpayConstant.NATIVE.equals(request.getTradeType())) {
					// 扫码支付二维码
					if (!StringUtils.isEmpty(resp.getCodeUrl())) {
						MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

						Map<EncodeHintType, Object> hints = Maps.newHashMap();
						hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
						BitMatrix bitMatrix = multiFormatWriter.encode(resp.getCodeUrl(), BarcodeFormat.QR_CODE, 500,
								500, hints);
						BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
						ByteArrayOutputStream os = new ByteArrayOutputStream();
						ImageIO.write(image, "png", os);
						String key = new StringBuilder().append("wxpay/")
								.append(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)).append("/")
								.append(UUID.randomUUID().toString()).append(".png").toString();
						aliOssService.putOss(key, new ByteArrayInputStream(os.toByteArray()));
						submitReq.setQrcodeUrl(appConfig.getOssUrl() + "/" + key);
						submitReq.setCodeUrl(resp.getCodeUrl());
						submitReq.setPrepayId(resp.getPrepayId());
						submitReq.setPartnerId(resp.getMchId());
					}
				}

				return submitReq;
			}else{
				throw new Exception("统一下单失败");
			}
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.pencilisland.pay.api.service.impl.WxpayService#closeOrder(com.
	 * pencilisland.pay.api.bean.wxpay.WxpayCloseOrderReq)
	 */
	@Override
	public WxpayAppSubmitReq closeOrder(WxpayCloseOrderReq request) throws Exception {

		String signKey = appConfig.getWxpaySignKey();

		request.setAppId(appConfig.getWxpayAppId());
		request.setMchId(appConfig.getWxpayMchId());
		request.setNonceStr(RandomUtil.generateString(20));

		// 生成签名串
		request.setSign(wxpaySign(request, signKey));

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Arrays.asList(WxpayConstant.APPLICAIONT_XML_UTF8));
		headers.setContentType(WxpayConstant.APPLICAIONT_XML_UTF8);

		HttpEntity<String> httpEntity = new HttpEntity<>(JaxbUtil.convertToXml(request), headers);
		String respXml = rest.postForObject(WxpayConstant.CLOSE_ORDER_URL, httpEntity, String.class);

		if (!StringUtils.isEmpty(respXml)) {
			log.info("关闭订单返回【{}】", respXml);
		}

		return null;
	}

	public String getSandboxSignKey() throws Exception {
		WxpaySandboxSignKeyReq req = new WxpaySandboxSignKeyReq();
		req.setMchId(appConfig.getWxpayMchId());
		req.setNonceStr(RandomUtil.generateString(20));
		req.setSign(wxpaySign(req, appConfig.getWxpaySignKey()));

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Arrays.asList(WxpayConstant.APPLICAIONT_XML_UTF8));
		headers.setContentType(WxpayConstant.APPLICAIONT_XML_UTF8);

		HttpEntity<String> httpEntity = new HttpEntity<>(JaxbUtil.convertToXml(req), headers);
		String respXml = rest.postForObject(WxpayConstant.SANDBOX_SIGN_KEY_URL, httpEntity, String.class);
		log.info("沙箱KEY返回【{}】", respXml);
		if (!StringUtils.isEmpty(respXml)) {
			WxpaySandboxSignKeyResp resp = JaxbUtil.converyToJavaBean(respXml, WxpaySandboxSignKeyResp.class);

			if (WxpayConstant.SUCCESS.equals(resp.getReturnCode())) {
				return resp.getSandboxSignkey();
			}
		}

		return null;
	}

	public <T> String wxpaySign(T request, String signKey) throws Exception {

		Field[] fields = request.getClass().getDeclaredFields();

		List<String> fieldNameList = new ArrayList<String>();

		Map<String, Object> reqMap = new HashMap<String, Object>();

		for (int i = 0; i < fields.length; i++) {
			String name = fields[i].getName();
			String key = fields[i].getAnnotation(XmlElement.class).name();
			fieldNameList.add(key);
			name = name.substring(0, 1).toUpperCase() + name.substring(1);
			Method m = request.getClass().getMethod("get" + name);
			reqMap.put(key, m.invoke(request));
		}

		String[] fieldNameArray = new String[fieldNameList.size()];
		fieldNameList.toArray(fieldNameArray);
		Arrays.sort(fieldNameArray);

		StringBuilder signStr = new StringBuilder();
		for (int i = 0; i < fieldNameArray.length; i++) {
			Object value = reqMap.get(fieldNameArray[i]);
			if (!StringUtils.isEmpty(value)) {
				if (signStr.length() == 0)
					signStr.append(fieldNameArray[i]).append("=").append(value);
				else
					signStr.append("&").append(fieldNameArray[i]).append("=").append(value);
			}
		}

		signStr.append("&").append("key=").append(signKey);

		log.info("待签字符串[{}]", signStr.toString());

		MessageDigest digest = MessageDigest.getInstance("MD5");
		digest.update(signStr.toString().getBytes("UTF-8"));

		String sign = Hex.encodeHexString(digest.digest()).toUpperCase();

		return sign;

	}

	@Override
	public WxLoginResp login(String code) throws Exception {
		StringBuilder urlBuilder = new StringBuilder();
		urlBuilder.append("https://api.weixin.qq.com/sns/jscode2session?appid=").append(appConfig.getWxappAppId())
				.append("&secret=").append(appConfig.getWxappAppSecret()).append("&js_code=").append(code).append("&grant_type=authorization_code");
		String result = rest.getForObject(urlBuilder.toString(), String.class);
		return objectMapper.readValue(result, WxLoginResp.class);
	}

}
